//
//  Helper.cpp
//  SGD
//
//  Created by Niketan Pansare on 12/19/12.
//  Copyright (c) 2012 Rice University. All rights reserved.
//

#include "Helper.h"

// ----------------------------------------------------------
// Comparison operators
inline bool operator==(const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs) {return lhs.logVal == rhs.logVal;}
inline bool operator< (const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs) {return lhs.logVal < rhs.logVal;}
inline bool operator!=(const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs) {return !operator==(lhs,rhs);}
inline bool operator> (const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs){return  operator< (rhs,lhs);}
inline bool operator<=(const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs){return !operator< (lhs,rhs);}

// Arithmetic operators
LightWeightBigDouble operator+(const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs) {
  return LightWeightBigDouble(true, LightWeightBigDouble::log_add(lhs.logVal, rhs.logVal));
}
LightWeightBigDouble operator-(const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs) {
  return LightWeightBigDouble(true, LightWeightBigDouble::log_subtract(lhs.logVal, rhs.logVal));
}
LightWeightBigDouble operator*(const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs) {
  return LightWeightBigDouble(true, lhs.logVal + rhs.logVal);
}
LightWeightBigDouble operator/(const LightWeightBigDouble& lhs, const LightWeightBigDouble& rhs) {
  return LightWeightBigDouble(true, lhs.logVal - rhs.logVal);
}
LightWeightBigDouble operator++(const LightWeightBigDouble& me) {
  // Since log(1) = 0
  return LightWeightBigDouble(true, LightWeightBigDouble::log_add(me.logVal,0));
}
LightWeightBigDouble operator++(const LightWeightBigDouble& me, int ignoreMe) {
  // Since log(1) = 0
  return LightWeightBigDouble(true, LightWeightBigDouble::log_add(me.logVal,0));
}
LightWeightBigDouble operator--(const LightWeightBigDouble& me) {
  // Since log(1) = 0
  return LightWeightBigDouble(true, LightWeightBigDouble::log_subtract(me.logVal,0));
}
LightWeightBigDouble operator--(const LightWeightBigDouble& me, int ignoreMe) {
  // Since log(1) = 0
  return LightWeightBigDouble(true, LightWeightBigDouble::log_subtract(me.logVal,0));
}

// Compound
LightWeightBigDouble& LightWeightBigDouble :: operator+=(const LightWeightBigDouble& rhs) {
  this->logVal = LightWeightBigDouble::log_add(this->logVal, rhs.logVal);
  return *this;
}
LightWeightBigDouble& LightWeightBigDouble :: operator-=(const LightWeightBigDouble& rhs) {
  this->logVal = LightWeightBigDouble::log_subtract(this->logVal, rhs.logVal);
  return *this;
}
LightWeightBigDouble& LightWeightBigDouble :: operator*=(const LightWeightBigDouble& rhs) {
  this->logVal = this->logVal + rhs.logVal;
  return *this;
}
LightWeightBigDouble& LightWeightBigDouble :: operator/=(const LightWeightBigDouble& rhs) {
  this->logVal = this->logVal - rhs.logVal;
  return *this;
}

bool LightWeightBigDouble :: IsEqual(double a, double b) {
  return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}

// Returns log(a' + b') where a = log(a') and b = log(b')
double LightWeightBigDouble :: log_add(double a, double b) {
  // Formula: log(exp(a) + exp(b)) = log(exp(a - m) + exp(b - m)) + m ... m=max(a,b)
  
  // Note, a > b
  double min = a < b ? a : b;
  double max = a > b ? a : b;
  
  double temp = 1 + exp(min - max);
  assert(temp != 0);
  return max + log(temp);
}

// Returns log(a' - b') where a = log(a') and b = log(b')
double LightWeightBigDouble :: log_subtract(double a, double b) {
  if(a <= b) {
    cout << "Error: Computing the log of a negative number\n";
    assert(false);
  }
  if(b == -std::numeric_limits<double>::max()) {
    // b represents log(0)
    return a;
  }
  double temp = 1 - exp(b - a);
  assert(temp != 0);
  return a + log(temp);
}

void LightWeightBigDouble :: setValue(double value) {
  if(LightWeightBigDouble :: IsEqual(value, 0)) {
    logVal = -std::numeric_limits<double>::max();
  }
  else {
    logVal = log(value);
  }
}

void LightWeightBigDouble :: init(bool isInitialValInLogSpace, double initialValue) {
  if(!isInitialValInLogSpace) {
    setValue(initialValue);
  }
  else {
    // In log space
    logVal = initialValue;
  }
}

LightWeightBigDouble& LightWeightBigDouble :: operator= (const LightWeightBigDouble& copyFrom) {
  if (this != &copyFrom) { // Check for self-assignment. Not imp in our class
    logVal = copyFrom.logVal;
  }
  return *this;
}

std::ostream& operator<<(std::ostream &os, const LightWeightBigDouble &me) {
  os << exp(me.logVal);
  return os;
}

std::istream& operator>>(std::istream &is, LightWeightBigDouble &me) {
  double val;
  is >> val;
  me.setValue(val);
  return is;
}

// ----------------------------------------------------------

vector<string> StringHelper :: split(const string& s, const string& delim, const bool keep_empty) {
  vector<string> result;
  if (delim.empty()) {
    result.push_back(s);
    return result;
  }
  string::const_iterator substart = s.begin(), subend;
  while (true) {
    subend = search(substart, s.end(), delim.begin(), delim.end());
    string temp(substart, subend);
    if (keep_empty || !temp.empty()) {
      result.push_back(temp);
    }
    if (subend == s.end()) {
      break;
    }
    substart = subend + delim.size();
  }
  return result;
}

// Returns "" string if unsuccessful
string StringHelper :: getStringFromSharedDataFile(string sharedDataFilePath, string key) {
  string line;
  StringHelper strHelper;
  ifstream myfile(sharedDataFilePath.c_str());
  if (myfile.is_open()) {
    while(myfile.good()) {
      getline (myfile,line);
      vector<string> elements = strHelper.split(line, "=");
      if(elements.size() == 2 && elements[0].compare(key) == 0) {
        return elements[1]; // This means there cannot be 2 "="
      }
    }
    myfile.close();
  }
  string emptyStr = string();
  return emptyStr;
}

string StringHelper :: rtrim(string str) {
  size_t endpos = str.find_last_not_of(" \t");
  if( string::npos != endpos ) {
    str = str.substr( 0, endpos+1 );
  }
  return str;
}

void StringHelper :: outputTextFileToStdOutput(string fileName, string replaceNewLineByThis) {
  string line;
  ifstream myfile (fileName.c_str());
  if (myfile.is_open()) {
    while (getline (myfile,line)) {
      cout << line << replaceNewLineByThis;
    }
    myfile.close();
  }
}

void StringHelper :: appendMessageToLogFile(string logFilePath, string message) {
  ofstream myfile;
  myfile.open(logFilePath.c_str(), std::ofstream::out | std::ofstream::app);
  myfile << message << "\n";
  myfile.close();
}

double* StringHelper :: getArrayFromSharedDataFile(string sharedDataFilePath, string key, int numDimensions) {
  string valueStr = getStringFromSharedDataFile(sharedDataFilePath, key);
  double* retVal = NULL;
  if(!valueStr.empty()) {
    vector<string> elements = split(valueStr, " ");
    retVal = new double[numDimensions];
    if(elements.size() >= numDimensions) {
      for(int i = 0; i < numDimensions; i++) {
        retVal[i] = atof(elements[i].c_str());
      }
    }
  }
  return retVal;
}

int StringHelper :: getIntegerFromSharedDataFile(string sharedDataFilePath, string key, int failedValue) {
  string valueStr = getStringFromSharedDataFile(sharedDataFilePath, key);

  if(!valueStr.empty()) {
    return atoi(valueStr.c_str());
  }
  else {
    return failedValue;
  }
}

RandomAccessTextFile :: RandomAccessTextFile(string inputFileName) {
  fin.open(inputFileName.c_str());
}

RandomAccessTextFile :: ~RandomAccessTextFile() {
  fin.close();
}

string RandomAccessTextFile :: getIthLine(int i) {
  string line;
  if(positionNumbers.size() == 0) {
    // Only once - Read position numbers of the file into vector for faster access
    
    fin.seekg (0, ios::beg); // Goto the first line
    positionNumbers.push_back(fin.tellg());
    while(getline(fin, line)) {
      positionNumbers.push_back(fin.tellg());
    }
    // Remove last position as it is EOF
    positionNumbers.pop_back();
  }
  
  assert(i < positionNumbers.size() && i >= 0);
  
  // Go to the line number and read the line
  if(fin.eof()) {
    fin.clear(); // Clear eof flag
  }
  fin.seekg(positionNumbers[i]);
  getline(fin, line);
  
  return line;
}

int RandomAccessTextFile :: getNumberOfLinesInTheFile() {
  string temp = getIthLine(0);
  return (int)positionNumbers.size();
}

void RandomAccessTextFile :: getIthItem(int i, int numDimensions, double* retX, double* retY) {
  // Now parse the line
  for(int j = 0; j < numDimensions; j++) {
    retX[j] = 0;
  }
  
  StringHelper strHelper;

  string line = getIthLine(i);
  const vector<string> words = strHelper.split(line, " ", false);
  if(words.size() < 2) {
    cout << "Error: Incorrect format in input file on line number " << i << ": >" << line << "<\n";
    exit(1);
  }
  *retY = atof(words[0].c_str());
  for(int j = 1; j < words.size(); j++) {
    const vector<string> temp = strHelper.split(words[j], ":", false);
    // Has to be of format featureNum:featureValue
    assert(temp.size() == 2);
    
    int featureNum = atoi(temp[0].c_str());
    double featureValue = atof(temp[1].c_str());
    
    // featureNum starts with 1
    assert(featureNum > 0 && featureNum <= numDimensions);
    
    // Else duplicate featureNum in a single line ... Is this an error or should it just be warning ?
    assert(retX[featureNum - 1] == 0);
    
    retX[featureNum - 1] = featureValue;
  }
}

bool IsFiniteNumber(double x)
{
  return (x <= DBL_MAX && x >= -DBL_MAX);
}